package contrl

import (
	"core/env"
	"core/graylog"
	"core/minio_client"
	"core/mysql"
	"core/rds"
	"core/utils"
	"db"
	"fmt"
	"github.com/go-redis/redis"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/minio/minio-go"
)

type PictureContrl struct{}

const (
	PicListOrderNew  = "new"
	PicListOrderLove = "love"
)

type PicUploadRes struct {
	GoldNum int `json:"goldNum"`
}

func (*PictureContrl) Upload(c *gin.Context) (res interface{}, e Error) {
	width := utils.ToInt(c.PostForm("width"))
	height := utils.ToInt(c.PostForm("height"))
	comment := c.PostForm("comment")
	name := c.PostForm("name")
	if width*height <= 0 {
		return nil, GenErrorMsg(fmt.Sprintf("宽高参数错误,%d,%d", width, height))
	}
	sign := GetSign(c)
	if !db.Lock(sign.Uid) {
		return nil, GenErrorMsg("您太快了，稍微慢些哦")
	}
	defer db.Unlock(sign.Uid)

	// 一系列判断
	user, err := db.LoadByUID(sign.Uid, false)
	if err != nil {
		return nil, GenError(err)
	}
	if user.Grass < 1 {
		return nil, GenErrorMsg("四叶草不足")
	}
	file, err := c.FormFile("file")
	if err != nil {
		return nil, GenError(err)
	}
	if !strings.Contains(file.Filename, ".") {
		return nil, GenErrorMsg("请上传正确的图片类型")
	}
	index := strings.LastIndex(file.Filename, ".")
	if index >= len(file.Filename) {
		return nil, GenErrorMsg("请上传正确的图片类型.")
	}
	ext := path.Ext(file.Filename)
	var contentType string
	switch ext {
	case ".png":
		contentType = "image/png"
	case ".jpg", ".jpeg":
		contentType = "image/jpg"
	default:
		return nil, GenErrorMsg("请上传正确的图片类型..")
	}

	// 扣除四叶草
	user.Grass--
	err = user.Save(db.FieldGrass)
	if err != nil {
		return nil, GenError(err)
	}
	defer func() {
		if err != nil {
			user.Grass++
			_ = user.Save(db.FieldGrass)
		}
	}()

	// 保存到我的上传记录中
	nowUnix := time.Now().Unix()
	uploadId := nowUnix
	err = db.AddPicUpload(user.UID, db.PicUploadItem{
		ID:       uploadId,
		Name:     name,
		CreateAt: nowUnix,
		Status:   db.PicUploadStatusInit,
		ConfId:   0,
	})
	if err != nil {
		return nil, GenError(err)
	}

	// 保存到mysql
	now := time.Now()
	imageName := fmt.Sprintf("%d-%d%s", sign.Uid, now.Unix(), ext)
	picture := &db.PicOriginalMysql{
		Name:         imageName,
		OriginName:   name,
		Width:        width,
		Height:       height,
		Comment:      comment,
		AuthorUID:    user.UID,
		AuthorName:   user.Name,
		UploadId:     uploadId,
		UploadAtUnix: nowUnix,
	}
	err = mysql.Create(picture)
	if err != nil {
		return nil, GenError(err)
	}

	go func() {
		var err error
		defer utils.CatchPanicAndErr("picUpload", &err, graylog.M{"uid": user.UID, "name": name})
		graylog.Info("上传图片", graylog.M{"playerId": user.UID, "name": name, "comment": comment, "width": width, "height": height})
		// 上传到图片服务器
		fileReader, err := file.Open()
		if err != nil {
			return
		}
		defer fileReader.Close()

		// 上传文件
		if bucketExist, _ := minio_client.Client.BucketExists(db.PicBucket); !bucketExist {
			err = minio_client.Client.MakeBucket(db.PicBucket, "us-east-1")
			if err != nil {
				return
			}
		}
		_, err = minio_client.Client.PutObject(db.PicBucket, imageName, fileReader, file.Size, minio.PutObjectOptions{ContentType: contentType})
		if err != nil {
			return
		}
		err = db.PushToResizePicList(imageName)
		return
	}()

	res = PicUploadRes{
		GoldNum: user.Grass,
	}
	return
}

type PicItem struct {
	ID                int64   `json:"id"`
	Comment           string  `json:"comment"`
	AuthorName        string  `json:"authorName"`
	AuthorUID         int64   `json:"authorUID"`
	UploadId          int64   `json:"upload_id"`
	UploadAtUnix      int64   `json:"upload_at_unix"`
	Online            bool    `json:"online"`
	Love              int     `json:"love"`
	JigsawName        string  `json:"jigsawName"`
	DragonBonesName   string  `json:"dragonBonesName"`
	JigsawSizeId      int     `json:"jigsawSizeId"`
	JigsawListId      int     `json:"jigsawListId"`
	ListIndexId       int     `json:"listIndexId"`
	IsRotate          bool    `json:"isRotate"`
	IsShow            bool    `json:"isShow"`
	FixedNum          int     `json:"fixedNum"`
	StartNum          int     `json:"startNum"`
	GoldNum           int     `json:"goldNum"`
	MiniIntro         string  `json:"miniIntro"`
	Water             int     `json:"water"`
	MusicName         string  `json:"musicName"`
	ShareImgName      string  `json:"shareImgName"`
	AnimateFullScreen string  `json:"animateFullScreen"`
	CreateTime        int64   `json:"createTime"`
	Animate           [][]int `json:"animate"`
}

type PicList struct {
	List []PicItem
}

// 线上图片列表
func (*PictureContrl) List(c *gin.Context) (res interface{}, e Error) {
	orderType := c.DefaultQuery("order", PicListOrderNew)
	start := utils.ToInt64(c.DefaultQuery("start", "0"))
	limit := utils.ToInt64(c.DefaultQuery("limit", "1000"))

	var rankKey string
	switch orderType {
	case PicListOrderNew:
		rankKey = db.MakePicRankKeyNew()
	case PicListOrderLove:
		rankKey = db.MakePicRankKeyLove()
	}
	picRedis := rds.GetPicClient()
	rankList, _ := picRedis.LoadRankList(rankKey, start, limit)
	list := make([]PicItem, 0)
	var err error
	for _, item := range rankList {
		picConfig := new(db.PicOnline)
		picConfig, err = db.LoadPicConfig(utils.ToInt64(item.Member))
		if err != nil {
			utils.Error(err)
			continue
		}
		if !picConfig.Online {
			continue
		}
		list = append(list, PicItem{
			ID:                picConfig.ID,
			Comment:           picConfig.Comment,
			AuthorName:        picConfig.AuthorName,
			AuthorUID:         picConfig.AuthorUID,
			UploadId:          picConfig.UploadId,
			UploadAtUnix:      picConfig.UploadAtUnix,
			Online:            picConfig.Online,
			Love:              picConfig.Love,
			JigsawName:        picConfig.OriginName,
			DragonBonesName:   picConfig.DragonBonesName,
			JigsawSizeId:      picConfig.JigsawSizeId,
			JigsawListId:      picConfig.JigsawListId,
			ListIndexId:       picConfig.ListIndexId,
			IsRotate:          picConfig.IsRotate,
			IsShow:            picConfig.IsShow,
			FixedNum:          picConfig.FixedNum,
			StartNum:          picConfig.StartNum,
			GoldNum:           picConfig.GoldNum,
			MiniIntro:         picConfig.MiniIntro,
			Water:             picConfig.Water,
			MusicName:         picConfig.MusicName,
			ShareImgName:      picConfig.ShareImgName,
			AnimateFullScreen: picConfig.AnimateFullScreen,
			CreateTime:        picConfig.CreateTime,
			Animate:           make([][]int, 0),
		})
	}

	res = PicList{
		List: list,
	}
	return
}

type PicUnlockRes struct {
	GoldNum int `json:"goldNum"`
	CustomLevel
}

// 解锁
func (*PictureContrl) Unlock(c *gin.Context) (res interface{}, e Error) {
	picId := GetInt(c, "pic_id")
	if picId < 1 {
		return nil, GenErrorMsg("参数错误id")
	}
	picConfig, err := db.LoadPicConfig(int64(picId))
	if err != nil {
		return nil, GenError(err)
	}
	if !picConfig.Online {
		return nil, GenErrorMsg("图片正在开发中，敬请期待")
	}

	sign := GetSign(c)
	if !db.Lock(sign.Uid) {
		return nil, GenErrorMsg("您太快了，稍微慢些哦")
	}
	defer db.Unlock(sign.Uid)

	u, err := db.LoadByUID(sign.Uid, true)
	if err != nil {
		return nil, GenError(err)
	}
	if u.Level[db.CustomMenuID] == nil {
		u.Level[db.CustomMenuID] = make(map[int]db.LevelDetail)
	}
	detail := u.Level[db.CustomMenuID][picId]
	if detail.Unlock {
		return nil, GenErrorMsg("请不要重复解锁")
	}

	if u.Grass < picConfig.GoldNum {
		return nil, GenErrorMsg("四叶草不足")
	}
	u.Grass -= picConfig.GoldNum
	err = u.Save(db.FieldGrass)
	if err != nil {
		return nil, GenError(err)
	}
	detail.Unlock = true
	err = u.SaveLevel(db.CustomMenuID, picId, &detail)
	if err != nil {
		u.Grass += picConfig.GoldNum
		err = u.Save(db.FieldGrass)
		return nil, GenError(err)
	}

	go graylog.Info("解锁拼图", graylog.M{
		"playerId": u.UID,
		"name":     u.Name,
		"menuId":   db.CustomMenuID,
		"levelId":  picId,
		"grass":    u.Grass,
		"levels":   fmt.Sprintf("%+v", u.Level),
	})
	res = PicUnlockRes{
		GoldNum:     u.Grass,
		CustomLevel: genCustomLevels(u),
	}
	return
}

type BeforeRankInfo struct {
	OldRank      int64
	NewRank      int64
	BeforePlayer RankPlayer
}

func updateRankAndLoadBeforePlayer(uid int64, water int, addWater int) (info BeforeRankInfo) {
	utils.SageGoAndCatchErr(func() (err error) {
		rankClient := rds.GetDefaultClient()
		rankKey := db.MakeWaterRankKey(uid)
		member := strconv.FormatInt(uid, 10)
		info.OldRank, err = rankClient.LoadMyRank(rankKey, member)
		if err == redis.Nil {
			err = nil
			return
		} else if err != nil {
			utils.Error(err)
			return
		}
		// 只有增加水滴的时候，才会更新排行榜
		if addWater > 0 {
			if err = rankClient.UpdateRank(rankKey, member, int64(water), db.WaterRankLimit); err != nil {
				utils.Error(err)
				return
			}
			if info.NewRank, err = rankClient.LoadMyRank(rankKey, member); err != nil {
				utils.Error(err)
				return
			}
			if info.NewRank < info.OldRank {
				var rankList []rds.BasicRankInfo
				rankList, err = rankClient.LoadRankList(rankKey, info.NewRank, info.NewRank+1)
				if err != nil {
					utils.Error(err)
					return
				}
				if len(rankList) == 0 {
					utils.Info("没有找到最后一名")
					return
				}
				utils.InfoJSON(rankList)
				fid := utils.ToInt64(rankList[0].Member)
				var friend *db.User
				friend, err = db.LoadByUID(fid, false)
				if err != nil {
					utils.Error(err, rankList)
					return
				}
				info.BeforePlayer = RankPlayer{
					BasicPlayer: genBasicPlayer(friend),
					Rank:        rankList[0].Rank,
					Score:       rankList[0].Score,
				}
			}
		} else {
			info.NewRank = info.OldRank
		}
		return
	})
	return
}

type PicFinishRes struct {
	HasCardPkg bool `json:"hasCardPkg"`
	GoldNum    int  `json:"goldNum"`
	AddWater   int
	Water      int
	BeforeRankInfo
	CustomLevel
}

// 完成
func (*PictureContrl) Finish(c *gin.Context) (res interface{}, e Error) {
	picId := GetInt(c, "pic_id")
	if picId < 1 {
		return nil, GenErrorMsg("参数错误id")
	}
	pic, err := db.LoadPicConfig(int64(picId))
	if err != nil {
		return nil, GenError(err)
	}

	sign := GetSign(c)
	if !db.Lock(sign.Uid) {
		return nil, GenErrorMsg("您太快了，稍微慢些哦")
	}
	defer db.Unlock(sign.Uid)

	u, err := db.LoadByUID(sign.Uid, true)
	if err != nil {
		return nil, GenError(err)
	}
	if u.Level[db.CustomMenuID] == nil {
		return nil, GenErrorMsg("请先解锁拼图")
	}
	detail, found := u.Level[db.CustomMenuID][picId]
	if !found {
		return nil, GenErrorMsg("请先解锁拼图哦")
	}

	hasCardPkg := true
	var addWater int
	if detail.FinishNum > 0 {
		hasCardPkg = env.HasCardPkg()
	} else if detail.FinishNum == 0 {
		u.Water += pic.Water
		addWater = pic.Water
	}
	u.FinishLevelAt = time.Now()

	err = u.Save(db.FieldFinishLevelAt, db.FieldWater)
	if err != nil {
		return nil, GenError(err)
	}
	detail.FinishNum++
	err = u.SaveLevel(db.CustomMenuID, picId, &detail)
	if err != nil {
		return nil, GenError(err)
	}

	go graylog.Info("完成拼图", graylog.M{
		"playerId":    u.UID,
		"name":        u.Name,
		"menuId":      db.CustomMenuID,
		"picId":       picId,
		"water":       u.Water,
		"finishCount": detail.FinishNum,
		"levels":      fmt.Sprintf("%+v", u.Level),
	})
	res = PicFinishRes{
		HasCardPkg:     hasCardPkg,
		GoldNum:        u.Grass,
		Water:          u.Water,
		AddWater:       addWater,
		BeforeRankInfo: updateRankAndLoadBeforePlayer(u.UID, u.Water, addWater),
		CustomLevel:    genCustomLevels(u),
	}
	return
}

type PicLoveRes struct {
	LoveCount int64
	CustomLevel
}

// 点赞
func (*PictureContrl) Love(c *gin.Context) (res interface{}, e Error) {
	picId := GetInt(c, "pic_id")
	if picId < 1 {
		return nil, GenErrorMsg("参数错误id")
	}
	picConfig, err := db.LoadPicConfig(int64(picId))
	if err != nil {
		return nil, GenError(err)
	}
	if !picConfig.Online {
		return nil, GenErrorMsg("图片正在开发中，敬请期待")
	}

	sign := GetSign(c)
	if !db.Lock(sign.Uid) {
		return nil, GenErrorMsg("您太快了，稍微慢些哦")
	}
	defer db.Unlock(sign.Uid)

	u, err := db.LoadByUID(sign.Uid, true)
	if err != nil {
		return nil, GenError(err)
	}
	loveKey := db.EncodeLove(db.CustomMenuID, picId)
	if u.Loves[loveKey] > 0 {
		return nil, GenErrorMsg("您已点赞")
	}

	// 更新用户点赞列表
	u.Loves[loveKey] = 1
	err = u.Save(db.FieldLoves)
	if err != nil {
		return nil, GenError(err)
	}

	// 更新图片点赞数
	loveCount, err := db.IncPicProp(int64(picId), db.PicFieldLove, 1)
	if err != nil {
		return nil, GenError(err)
	}

	go graylog.Info("点赞拼图", graylog.M{
		"playerId": u.UID,
		"picId":    picId,
	})
	res = PicLoveRes{
		LoveCount:   loveCount,
		CustomLevel: genCustomLevels(u),
	}
	return
}

func (*PictureContrl) Debug(c *gin.Context) (res interface{}, e Error) {
	rankKey := db.MakePicRankKeyNew()
	picRedis := rds.GetPicClient()
	rankList, _ := picRedis.LoadRankList(rankKey, 0, 1000)
	for _, item := range rankList {
		picConfig, _ := db.LoadPicConfig(utils.ToInt64(item.Member))
		utils.InfoJSON(picConfig)
		mysql.Save(picConfig.ID, picConfig)
	}
	res = rankList
	return
}
